using System;
using System.Collections.Generic;
using System.Text;
using System.IO.Packaging;
using System.IO;
using System.Security.Cryptography.Xml;
using System.Xml; //WindowsBase dll

namespace PlainConcepts.Fx.Dnie.DigitalSign.Plugins
{
    /// <summary>
    /// Plugin to sign Word 2007 Documents
    /// <example>
    /// <code>
    /// </code>
    /// </example>
    /// </summary>
    class WordPackageDigitalSigner
        :IDigitalSigner
    {
        #region Properties

        private string mSignatureId = Resources.Strings.SignatureId;
        /// <summary>
        /// Get or Set the signatureId
        /// </summary>
        public string SignatureId
        {
            get
            {
                return mSignatureId;
            }
        }

        private string mManifestHashAlgorithm = Resources.Strings.ManifestHashAlgorithm;
        /// <summary>
        /// Get or set the manisfest Hash Algorithm
        /// </summary>
        public string ManifestHashAlgorithm
        {
            get
            {
                return mManifestHashAlgorithm;
            }
        }
        #endregion

        #region Constructors

        /// <summary>
        /// Default Constructor
        /// </summary>
        public WordPackageDigitalSigner()
        {
        }

        #endregion

        #region IDigitalSigner Members

        /// <summary>
        /// IDigitalSigner.Sign
        /// </seealso>
        /// </summary>
        /// <param name="certificate">IDigitalSigner.certificate/param>
        /// <param name="pathDocument">IDigitalSigner.pathDocument</param>
        /// <param name="pathSignedDocument">IDigitalSigner.pathSignedDocument</param>
        /// <exception cref="System.IO.FileFormatException>
        /// Package not valid
        /// </exception>
        public void Sign(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, string pathDocument, string pathSignedDocument)
        {
            //Validate if path exits
            Asserts.IsNullOrEmpty(pathDocument, "pathDocument");
            Asserts.IsNullOrEmpty(pathSignedDocument, "pathSignedDocument");
            Asserts.IsValidPath(pathDocument);
            Asserts.IsValidExtension(pathDocument, this.Extension);

            //Open openXml package
            Package wordPackage = OpenPackage(pathDocument);
            if (wordPackage != null)
            {
                List<Uri> partsToSign = new List<Uri>();
                List<PackageRelationshipSelector> relationShipsToSign = new List<PackageRelationshipSelector>();
                List<Uri> finishedItems = new List<Uri>();

                foreach (PackageRelationship relationShip 
                        in 
                        wordPackage.GetRelationshipsByType(Resources.Strings.RTOfficeDocuments)
                        )
                {
                    AddSignableItems(relationShip, partsToSign, relationShipsToSign);
                }

                //Create Digital Manager
                PackageDigitalSignatureManager digitalSignatureManager = new PackageDigitalSignatureManager(wordPackage);
                digitalSignatureManager.CertificateOption = CertificateEmbeddingOption.InSignaturePart;

                //Create OfficeObject with SignatureId and ManifestHashAlgorithm
                DataObject officeObject = CretateOfficeObject();

                Reference officeObjectReference = new Reference("#" + Resources.Strings.OfficeObjectId);

                //Sign openXml Document
                digitalSignatureManager.Sign(partsToSign,       
                                            certificate, 
                                            relationShipsToSign, 
                                            mSignatureId, 
                                            new DataObject[] { officeObject }, 
                                            new Reference[] { officeObjectReference }
                                            );

            }

        }
        /// <summary>
        /// Get the extension valid to sign with this plugin
        /// </summary>
        public string Extension
        {
            get { return ".docx"; }
        }

        #endregion

        #region Private Methods
        
        private Package OpenPackage(string pathDocument)
        {
            return Package.Open(pathDocument, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
        }

        private void AddSignableItems(
            PackageRelationship relationship,
            List<Uri> partsToSign,
            List<PackageRelationshipSelector> relationshipsToSign)
        {
            PackageRelationshipSelector selector =
                new PackageRelationshipSelector(
                    relationship.SourceUri,
                    PackageRelationshipSelectorType.Id,
                    relationship.Id);
            relationshipsToSign.Add(selector);
            if (relationship.TargetMode == TargetMode.Internal)
            {
                PackagePart part = relationship.Package.GetPart(
                    PackUriHelper.ResolvePartUri(
                        relationship.SourceUri, relationship.TargetUri));
                if (partsToSign.Contains(part.Uri) == false)
                {
                    partsToSign.Add(part.Uri);
                    foreach (PackageRelationship childRelationship in
                        part.GetRelationships())
                    {
                        AddSignableItems(childRelationship,
                            partsToSign, relationshipsToSign);
                    }
                }
            }
        }

        private DataObject CretateOfficeObject()
        {
            XmlDocument document = new XmlDocument();
            document.Load(string.Format(Resources.Strings.OfficeObject, this.mSignatureId, this.mManifestHashAlgorithm));

            DataObject officeObject = new DataObject();

            //Not change order
            officeObject.LoadXml(document.DocumentElement);
            officeObject.Id = Resources.Strings.OfficeObjectId;

            return officeObject;
            

        }
        #endregion
    }
}
